#include "window.h"

map<Uint32,GLEWContext*> Window::sGLEWContextMap;
map<Uint32,Window*> Window::sIdMap;
SDL_mutex* Window::sIdMapMutex = 0;

Window::Window(const string& name, ClassId* classId):
Instance(name, classId),
mRenderThread(0),
mRenderSem(0),
mRendering(false),
mWindow(0),
mGLContext(0),
mFlags(SDL_WINDOW_OPENGL),
mPositionX(SDL_WINDOWPOS_CENTERED),
mPositionY(SDL_WINDOWPOS_CENTERED),
mWidth(512),
mHeight(512)
{
}

Window::~Window()
{
    if(isRendering())
        close();
}

bool Window::open()
{
    try {
        if(isRendering())
            throw ("Window is already opened");
        mRenderSem = SDL_CreateSemaphore(0);
        if(!mRenderSem)
            throw ("Could not create render semaphore");
        mRenderThread = SDL_CreateThread(Window::renderThread, this);
        if(!mRenderThread)
            throw ("Could not create render thread");

    }
    catch (const char* error) {
        cerr << "Window::open() : " << error << endl;
        if(mRenderSem)
            SDL_DestroySemaphore(mRenderSem);
        return false;
    }
    // Let the render thread initialize correctly
    while(!isRendering())
        ;

    return true;
}

void Window::close()
{
    int retvalue;

    if(!isRendering())
        return;
    mRendering = false;
    SDL_SemPost(mRenderSem);
    SDL_WaitThread(mRenderThread, &retvalue);
    SDL_DestroySemaphore(mRenderSem);
}

void Window::nextFrame()
{
    SDL_SemPost(mRenderSem);
}

void Window::render()
{
    glClearColor(0.3f, 0.3f, 0.3f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    SDL_GL_SwapWindow(mWindow);
}

void Window::handleEvent(SDL_WindowEvent* event, float dt)
{
    assert(event);
    Window* window = Window::getFromId(event->windowID);

    if(!window)
        return;
    switch (event->event) {
        case SDL_WINDOWEVENT_SHOWN:
            window->onShow(dt);
            break;

        case SDL_WINDOWEVENT_HIDDEN:
            window->onHide(dt);
            break;

        case SDL_WINDOWEVENT_EXPOSED:
            window->onExpose(dt);
            break;

        case SDL_WINDOWEVENT_MOVED:
            window->onMove(event->data1, event->data2, dt);
            break;

        case SDL_WINDOWEVENT_RESIZED:
            window->onResize(event->data1, event->data2, dt);
            break;

        case SDL_WINDOWEVENT_SIZE_CHANGED:
            window->onSizeChange(event->data1, event->data2, dt);
            break;

        case SDL_WINDOWEVENT_MINIMIZED:
            window->onMinimize(dt);
            break;

        case SDL_WINDOWEVENT_MAXIMIZED:
            window->onMaximize(dt);
            break;

        case SDL_WINDOWEVENT_RESTORED:
            window->onRestore(dt);
            break;

        case SDL_WINDOWEVENT_ENTER:
            window->onEnter(dt);
            break;

        case SDL_WINDOWEVENT_LEAVE:
            window->onLeave(dt);
            break;

        case SDL_WINDOWEVENT_FOCUS_GAINED:
            window->onFocusGain(dt);
            break;

        case SDL_WINDOWEVENT_FOCUS_LOST:
            window->onFocusLose(dt);
            break;

        case SDL_WINDOWEVENT_CLOSE:
            window->onClose(dt);
            window->close();
            break;
    }
}

void Window::onShow(float dt)
{
}

void Window::onHide(float dt)
{
}

void Window::onExpose(float dt)
{
}

void Window::onMove(int dx, int dy, float dt)
{
}

void Window::onResize(int width, int height, float dt)
{
    SDL_GetWindowSize(mWindow, &mWidth, &mHeight);
}

void Window::onSizeChange(int width, int height, float dt)
{
    SDL_GetWindowSize(mWindow, &mWidth, &mHeight);
}

void Window::onMinimize(float dt)
{
}

void Window::onMaximize(float dt)
{
}

void Window::onRestore(float dt)
{
}

void Window::onEnter(float dt)
{
}

void Window::onLeave(float dt)
{
}

void Window::onFocusGain(float dt)
{
}

void Window::onFocusLose(float dt)
{
}

void Window::onClose(float dt)
{
    DEBUG_MARKER;
}

bool Window::load(DataStream* ds)
{
    INSTANCE_LOAD(ds);

    try {
        if (!(*ds >> mFlags))
            throw ("Could not load mFlags");
        if (!(*ds >> mPositionX))
            throw ("Could not load mPositionX");
        if (!(*ds >> mPositionY))
            throw ("Could not load mPositionY");
        if (!(*ds >> mWidth))
            throw ("Could not load mWidth");
        if (!(*ds >> mHeight))
            throw ("Could not load mHeight");
    }
    catch (const char *error) {
        cerr << "Window::load(ds) : " << error << endl;
        return false;
    }

    return true;
}

bool Window::save(DataStream* ds)
{
    INSTANCE_SAVE(ds);

    try {
        if (!(*ds << mFlags))
            throw ("Could not save mFlags");
        if (!(*ds << mPositionX))
            throw ("Could not save mPositionX");
        if (!(*ds << mPositionY))
            throw ("Could not save mPositionY");
        if (!(*ds << mWidth))
            throw ("Could not save mWidth");
        if (!(*ds << mHeight))
            throw ("Could not save mHeight");
    }
    catch (const char *error) {
        cerr << "Window::save(ds) : " << error << endl;
        return false;
    }

    return true;
}

GLEWContext* Window::getGLEWContext()
{
    return sGLEWContextMap[SDL_ThreadID()];
}

Window* Window::getFromId(Uint32 windowId)
{
    Window* retvalue;

    if (!sIdMapMutex)
        return 0;
    SDL_LockMutex(sIdMapMutex);
    retvalue = sIdMap[windowId];
    SDL_UnlockMutex(sIdMapMutex);
    return retvalue;
}

int Window::renderThread(void* data)
{
    Window* window = (Window*)data;

    try {
        // Set GL attributes
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

        if (!sIdMapMutex) {
            sIdMapMutex = SDL_CreateMutex();
            if (!sIdMapMutex)
                throw("Could not create ID map mutex");
        }

        // Create SDL window
        window->mWindow = SDL_CreateWindow(window->getName().c_str(),
                                           window->getPositionX(), window->getPositionY(),
                                           window->getWidth(), window->getHeight(),
                                           window->mFlags);
        if(!window->mWindow)
            throw ("Could not create SDL window");
        SDL_LockMutex(sIdMapMutex);
        sIdMap[SDL_GetWindowID(window->mWindow)] = window;
        SDL_UnlockMutex(sIdMapMutex);

        // Create GL context
        window->mGLContext = SDL_GL_CreateContext(window->mWindow);
        if(!window->mGLContext)
            throw("Could not create GL context");
        //SDL_GL_MakeCurrent(window->mWindow, window->mGLContext);

        // Create GLEW context
        sGLEWContextMap[SDL_ThreadID()] = new GLEWContext;
        GLenum error = glewContextInit(sGLEWContextMap[SDL_ThreadID()]);
        if (error != GLEW_OK)
            throw ("Could not create GLEW context");
    }
    catch (const char* error) {
        cerr << "Window::renderThread() : " << error << endl;
        if (sGLEWContextMap[SDL_ThreadID()]) {
            delete sGLEWContextMap[SDL_ThreadID()];
            sGLEWContextMap.erase(SDL_ThreadID());
        }
        if (window->mGLContext) {
            SDL_GL_DeleteContext(window->mGLContext);
            window->mGLContext = 0;
        }
        if (window->mWindow) {
            assert(sIdMapMutex);
            SDL_LockMutex(sIdMapMutex);
            sIdMap.erase(SDL_GetWindowID(window->mWindow));
            SDL_UnlockMutex(sIdMapMutex);
            SDL_DestroyWindow(window->mWindow);
            window->mWindow = 0;
        }
        if (sIdMapMutex) {
            if(!sIdMap.size()) {
                SDL_DestroyMutex(sIdMapMutex);
                sIdMapMutex = 0;
            }
        }
        return -1;
    }

    // Rendering loop
    window->mRendering = true;
    while(window->isRendering()) {
        if(!SDL_SemWait(window->mRenderSem))
            window->render();
    }

    SDL_LockMutex(sIdMapMutex);

    // Destroy GLEW context
    delete sGLEWContextMap[SDL_ThreadID()];
    sGLEWContextMap.erase(SDL_ThreadID());

    // Destroy GL context
    SDL_GL_DeleteContext(window->mGLContext);
    window->mGLContext = 0;

    // Destroy SDL Window
    sIdMap.erase(SDL_GetWindowID(window->mWindow));
    SDL_DestroyWindow(window->mWindow);
    window->mWindow = 0;

    SDL_UnlockMutex(sIdMapMutex);

    if(!sIdMap.size()) {
        SDL_DestroyMutex(sIdMapMutex);
        sIdMapMutex = 0;
    }

    return 0;
}
